package com.elitel.frame.business.service.impl;

import com.elitel.common.CustomHttpServletRequestWrapper;
import com.elitel.common.minio.MinioTemplate;
import com.elitel.frame.base.service.CfgFileParamService;
import com.elitel.frame.base.service.JdpCommFileService;
import com.elitel.frame.base.service.SysPrjServiceService;
import com.elitel.frame.business.service.NetcdfService;
import com.elitel.frame.core.common.FileUtil;
import com.elitel.frame.main.dao.BaseServicesMapper;
import com.elitel.frame.main.entity.BaseServices;
import com.elitel.frame.main.entity.CfgFileParam;
import com.elitel.frame.main.entity.JdpCommFile;
import com.elitel.frame.main.entity.SysPrjservice;
import com.elitel.frame.main.entity.vo.JsonDataResult;
import com.elitel.frame.main.entity.vo.PageResponseVo;
import lombok.Data;
import net.sf.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import ucar.ma2.ArrayDouble;
import ucar.ma2.DataType;
import ucar.ma2.InvalidRangeException;
import ucar.nc2.Attribute;
import ucar.nc2.Dimension;
import ucar.nc2.NetcdfFileWriter;
import ucar.nc2.Variable;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author hi_bj
 */
@Service
public class NetcdfServiceImpl implements NetcdfService {

    @Autowired
    WebApplicationContext webApplicationContext;
    @Autowired
    SysPrjServiceService sysPrjServiceService;
    @Resource
    private BaseServicesMapper baseServicesMapper;
    @Resource
    MinioTemplate minioTemplate;
    @Resource
    private CfgFileParamService cfgFileParamService;
    @Resource
    private JdpCommFileService jdpCommFileService;

    private static String TIME_DIALECT = "TM";
    private static String STATION_CODE_DIALECT = "STCD";
    private static String DAY_UNIT_DIALECT = "DAY";


    private Map<String, Object> initFileParam(CfgFileParam cfgFileParam) {
        HashMap<String, Object> fileParamMap = new HashMap<>(16);
        fileParamMap.put("file_param_id", cfgFileParam.getFileParamId());
        fileParamMap.put("service_id", cfgFileParam.getServiceId());
        fileParamMap.put("file_name", cfgFileParam.getFileName());
        fileParamMap.put("service_code", cfgFileParam.getTartgetServiceCode());
        fileParamMap.put("bgtm", cfgFileParam.getBgtm());
        fileParamMap.put("edtm", cfgFileParam.getEdtm());
        fileParamMap.put("dt", cfgFileParam.getDt());
        fileParamMap.put("dt_unit", cfgFileParam.getDtUnit());
        fileParamMap.put("stcd", cfgFileParam.getStcd());
        fileParamMap.put("dim_info", cfgFileParam.getDimInfo());
        fileParamMap.put("var_info", cfgFileParam.getVarInfo());
        fileParamMap.put("var_info_type", cfgFileParam.getVarInfoType());
        fileParamMap.put("attr_info", cfgFileParam.getAttrInfo());
        return fileParamMap;
    }

    @Override
    public void createNcFile(HttpServletRequest request) {
        List<CfgFileParam> cfgFileParams = this.cfgFileParamService.selectByFileStatus(0);
        for (CfgFileParam cfgFileParam : cfgFileParams) {
            try {
                this.createNcFile(request, cfgFileParam);
            } catch (Exception e) {
                e.printStackTrace();
                cfgFileParam.setFileStatus(-1);
                this.cfgFileParamService.updateByPrimaryKeySelective(cfgFileParam);
            }
        }
    }

    private void createNcFile(HttpServletRequest request, CfgFileParam cfgFileParam) {
        //0.获取文件配置参数
        Map<String, Object> fileParamMap = initFileParam(cfgFileParam);

        NetcdfFileConfiguration netcdfFileConfiguration = new NetcdfFileConfiguration();
        //1.基础信息配置
        netcdfFileConfiguration.setFileName(String.valueOf(fileParamMap.get("file_name")));
        netcdfFileConfiguration.setServiceCode(String.valueOf(fileParamMap.get("service_code")));

        //设置测站
        String[] stationCodes = String.valueOf(fileParamMap.get("stcd")).split(",");
        netcdfFileConfiguration.setStationCodes(stationCodes);

        //设置时间
        String beginTime = String.valueOf(fileParamMap.get("bgtm"));
        String endTime = String.valueOf(fileParamMap.get("edtm"));
        netcdfFileConfiguration.setBeginTime(beginTime);
        netcdfFileConfiguration.setEndTime(endTime);
        //设置时次
        List<String> timeList = new ArrayList<>();

        String dateUnit = String.valueOf(fileParamMap.get("dt_unit"));
        int dt = Integer.valueOf(String.valueOf(fileParamMap.get("dt")));

        Calendar calendar = Calendar.getInstance();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            calendar.setTime(sdf.parse(beginTime));
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
        for (; ; ) {
            timeList.add(sdf.format(calendar.getTime()));
            if (DAY_UNIT_DIALECT.equals(dateUnit)) {
                calendar.add(Calendar.DAY_OF_YEAR, dt);
            } else {
                calendar.add(Calendar.HOUR_OF_DAY, dt);
            }
            try {
                if (calendar.getTime().getTime() > sdf.parse(endTime).getTime()) {
                    break;
                }
            } catch (ParseException e) {
                throw new RuntimeException(e);
            }
        }
        netcdfFileConfiguration.setTimes(timeList.toArray(new String[0]));

        //2.维度
        Map<String, Integer> dimensionMap = new LinkedHashMap<>();
        String[] dimInfo = String.valueOf(fileParamMap.get("dim_info")).split(",");
        for (int i = 0; i < dimInfo.length; i++) {
            if (TIME_DIALECT.equals(dimInfo[i])) {
                dimensionMap.put(dimInfo[i], timeList.size());
            } else if (STATION_CODE_DIALECT.equals(dimInfo[i])) {
                dimensionMap.put(dimInfo[i], stationCodes.length);
            }
        }
        netcdfFileConfiguration.setDimensionMap(dimensionMap);

        //3.变量
        Map<String, String> variableMap = new LinkedHashMap<>();
        String[] varInfo = String.valueOf(fileParamMap.get("var_info")).split(",");
        String[] varInfoType = String.valueOf(fileParamMap.get("var_info_type")).split(",");
        for (int i = 0; i < varInfo.length; i++) {
            variableMap.put(varInfo[i], varInfoType[i]);
        }
        netcdfFileConfiguration.setVariableMap(variableMap);

        //4.注释帮助信息
        Map<String, String> attributeMap = new LinkedHashMap<>();
        Map<String, Object> stringObjectMap = JSONObject.fromObject(fileParamMap.get("attr_info"));
        for (Map.Entry<String, Object> entry : stringObjectMap.entrySet()) {
            attributeMap.put(entry.getKey(), String.valueOf(entry.getValue()));
        }
        attributeMap.put("BGTM", beginTime);
        attributeMap.put("EDTM", endTime);
        attributeMap.put("STCD_Info", String.valueOf(fileParamMap.get("stcd")));
        netcdfFileConfiguration.setAttributeMap(attributeMap);

        //5.生成NC文件
        createFileD3(request, netcdfFileConfiguration, cfgFileParam.getFileParamId());

        //6.删除文件
        FileUtil.delete(fileParamMap.get("file_name") + "");

        //7.文件信息入库,改同步状态
        this.saveFileParamAndComm(cfgFileParam);

    }


    private Object requestServiceData(HttpServletRequest request, String serviceUrl, String serviceId) throws InvocationTargetException, IllegalAccessException {
        HandlerMethod handlerMethod = null;
        RequestMappingHandlerMapping requestMappingHandlerMapping = webApplicationContext.getBean(RequestMappingHandlerMapping.class);
        // 获取 RequestMapping中的全部方法
        Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
        for (RequestMappingInfo requestMappingInfo : handlerMethodMap.keySet()) {
            //获取所有的URL
            Set<String> urlSet = requestMappingInfo.getPatternsCondition().getPatterns();
            if (!ObjectUtils.isEmpty(urlSet) && urlSet.contains(serviceUrl)) {
                handlerMethod = handlerMethodMap.get(requestMappingInfo);
                break;
            }
        }
        if (handlerMethod != null) {
            //获取当前Controller对应的 beanName
            String beanName = String.valueOf(handlerMethod.getBean());
            return handlerMethod.getMethod().invoke(webApplicationContext.getBean(beanName),
                    request, serviceId, "1", "10000", null, null);
        }
        return null;
    }


    /**
     * 创建Netcdf 3D
     */
    public void createFileD3(HttpServletRequest request, NetcdfFileConfiguration netcdfFileConfiguration, String fileParamId) {
        String filename = netcdfFileConfiguration.getFileName();
        // nc 文件名称
        NetcdfFileWriter dataFile = null;

        try {
            dataFile = NetcdfFileWriter.createNew(NetcdfFileWriter.Version.netcdf3, filename);
            //1.添加变量描述
            Map<String, String> attributeMap = netcdfFileConfiguration.getAttributeMap();
            for (Map.Entry<String, String> entry : attributeMap.entrySet()) {
                dataFile.addGroupAttribute(null, new Attribute(entry.getKey(), entry.getValue()));
            }
            //2.添加维度
            List<Dimension> dims = new ArrayList<>();
            for (Map.Entry<String, Integer> entry : netcdfFileConfiguration.getDimensionMap().entrySet()) {
                dataFile.addGroupAttribute(null, new Attribute(entry.getKey(), entry.getValue()));
                Dimension xDim = dataFile.addDimension(null, entry.getKey(), entry.getValue());
                dims.add(xDim);
            }
            //3.添加变量
            List<Variable> vars = new ArrayList<>();
            for (Map.Entry<String, String> entry : netcdfFileConfiguration.getVariableMap().entrySet()) {
                Variable var = dataFile.addVariable(null, entry.getKey(), DataType.getType(entry.getValue()), dims);
                vars.add(var);
            }
            //4.创建
            dataFile.create();

            // 二维数据
            if (netcdfFileConfiguration.getDimensionMap().size() == 2) {
                List<ArrayDouble.D2> d2Arraylist = new ArrayList<>();
                for (int k = 0; k < vars.size(); k++) {
                    ArrayDouble.D2 dataOut = new ArrayDouble.D2(dims.get(0).getLength(), dims.get(1).getLength());
                    d2Arraylist.add(dataOut);
                }
                //获取所有测站
                String[] stationCodes = netcdfFileConfiguration.getStationCodes();
                //获取所有时次
                String[] times = netcdfFileConfiguration.getTimes();
                //请求接口获取数据
                String serviceCode = netcdfFileConfiguration.getServiceCode();
                SysPrjservice sysPrjservice = sysPrjServiceService.getSysPrjserviceByServiceCode(serviceCode);
                //获取服务访问配置信息
                BaseServices baseServices1 = baseServicesMapper.selectByPrimaryKey(sysPrjservice.getServicetype());

                String beginTime = netcdfFileConfiguration.getBeginTime();
                String endTime = netcdfFileConfiguration.getEndTime();
                //遍历站点
                for (int i = 0; i < stationCodes.length; i++) {
                    HttpServletRequest httpServletRequest = new CustomHttpServletRequestWrapper(request);
                    Map<String, String[]> parameterMap = httpServletRequest.getParameterMap();
                    parameterMap.put("STCD", new String[]{stationCodes[i]});
                    parameterMap.put("BGTM", new String[]{beginTime});
                    parameterMap.put("EDTM", new String[]{endTime});
                    JsonDataResult jsonDataResult = (JsonDataResult) requestServiceData(httpServletRequest, baseServices1.getServiceurl(), serviceCode);
                    List<Map<String, Object>> resultList = ((PageResponseVo) jsonDataResult.getData()).getRows();
                    Map<String, Map<String, Object>> resultListMap = resultList.stream()
                            .collect(Collectors.toMap(item -> String.valueOf(item.get("TM")), Function.identity()));
                    for (int j = 0; j < times.length; j++) {
                        //遍历变量
                        for (int k = 0; k < vars.size(); k++) {
                            Object obj = Optional.ofNullable(resultListMap.get(times[j])).orElseGet(HashMap::new).get(vars.get(k).getShortName() + "");
                            d2Arraylist.get(k).set(j, i, ObjectUtils.isEmpty(obj) ? -99 : Double.parseDouble(obj + ""));
                        }
                    }
                }
                //遍历变量
                for (int k = 0; k < vars.size(); k++) {
                    //输出各个变量填充的数据
                    dataFile.write(vars.get(k), d2Arraylist.get(k));
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        } catch (InvalidRangeException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } finally {
            if (null != dataFile) {
                try {
                    dataFile.close();
                } catch (IOException ioe) {
                    ioe.printStackTrace();
                }
            }
        }

        try {
            minioTemplate.putObject("gdp", fileParamId, filename);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        System.out.println("*** SUCCESS writing example file simple_xy.nc!");
    }


    @Transactional(rollbackFor = Exception.class)
    public void saveFileParamAndComm(CfgFileParam cfgFileParam) {
        File file = new File(cfgFileParam.getFileName());
        // 新增文件数据
        JdpCommFile jdpCommFile = new JdpCommFile();
        jdpCommFile.setFileId(cfgFileParam.getFileParamId());
        jdpCommFile.setFileName(cfgFileParam.getFileName());
        // 根据文件id拿取文件
        jdpCommFile.setFilePath(cfgFileParam.getFileParamId());
        jdpCommFile.setFileSuffix(cfgFileParam.getFileType());
        jdpCommFile.setFileSize(file.length() + "");
        JdpCommFile commFile = this.jdpCommFileService.queryById(jdpCommFile.getFileId());
        if (commFile == null) {
            this.jdpCommFileService.insert(jdpCommFile);
        } else {
            this.jdpCommFileService.update(jdpCommFile);
        }
        // 更改文件上传状态
        cfgFileParam.setFileStatus(1);
        this.cfgFileParamService.updateByPrimaryKeySelective(cfgFileParam);
    }
}

@Data
class NetcdfFileConfiguration {
    String fileName;

    String serviceCode;

    String[] times;

    String[] stationCodes;
    String beginTime;
    String endTime;
    //示例{TM: 365}
    Map<String, Integer> dimensionMap = new LinkedHashMap<>();

    Map<String, String> variableMap = new LinkedHashMap<>();

    Map<String, String> attributeMap;

}